package com.easy.frame.base.auth;

import com.alibaba.fastjson.JSONObject;
import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import com.easy.frame.common.redis.RedisUtil;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.time.Duration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Callable;

/**
 * 权限相关工具类
 *
 * @author zzf
 * @date 2020/7/16 15:09
 */
public class AuthUtil {

    /**
     * 国际化-简体中文
     */
    private static final int LAN_CN = 1;

    /**
     * 国际化-繁体中文
     */
    private static final int LAN_FT = 2;

    /**
     * 国际化-英语
     */
    private static final int LAN_EN = 3;

    //用户权限本地缓存
    private static final Cache<Long, Set<String>> userRoleMap =
            Caffeine.newBuilder()
                    .expireAfterAccess(Duration.ofDays(1))
                    .build();


    /**
     * 生成token
     *
     * @param userId   用户id
     * @param language 用户默认语言
     */
    public static String generateToken(Long userId, Integer language) {
        String sign = JWT.create()
                .withClaim(AuthConstants.TOKEN_DETAIL_ID, userId)
                .withClaim(AuthConstants.TOKEN_DETAIL_LANGUAGE, language)
                .sign(Algorithm.HMAC256(String.valueOf(System.currentTimeMillis())));
        //保存到redis
        setUserToken(userId, sign);
        return sign;
    }

    /**
     * 保存用户token到redis
     */
    public static void setUserToken(Long userId, String token) {
        if (userId == null || token == null) {
            return;
        }
        RedisUtil.getInstance().set(AuthConstants.USER_TOKEN_KEY + userId, token);
    }


    /**
     * 获取当前用户id
     */
    public static Long getCurrentUserId() {
        DecodedJWT decode = getDecodedToken();
        return decode.getClaim(AuthConstants.TOKEN_DETAIL_ID).asLong();
    }

    /**
     * 获取指定用户的token
     */
    public static String getUserToken(Long userId) {
        if (userId == null) {
            return null;
        }
        return RedisUtil.getInstance().get(AuthConstants.USER_TOKEN_KEY + userId);
    }

    /**
     * 获取指定用户权限列表
     */
    public static List<String> getUserRoles(Long userId) {
        String s = RedisUtil.getInstance().get(userId.toString());
        if (s == null) {
            return null;
        }
        return JSONObject.parseArray(s, String.class);
    }

    /**
     * 缓存指定用户权限列表
     */
    public static void setUserRoles(Long userId, List<String> roleList) {
        if (userId == null) return;
        RedisUtil.getInstance().set(AuthConstants.ROLES_KEY + userId.toString(), roleList.toString());
    }


    /**
     * 获取当前用户权限
     */
    public static boolean checkRole(String role) {
        Long userId = getCurrentUserId();
        //查看本地缓存
        if (checkLocal(userId, role)) {
            return true;
        }
        //redis中查询
        List<String> userRoleList = getUserRoles(userId);
        if (userRoleList != null) {
            if (userRoleList.contains(role)) {
                cacheLocal(userId, role);
                return true;
            }
        }
        return false;
    }

    /**
     * 缓存权限信息到本地
     */
    private static void cacheLocal(Long userId, String role) {
        Set<String> roles = userRoleMap.getIfPresent(userId);
        if (roles == null) {
            roles = new HashSet<>();
            roles.add(role);
            userRoleMap.put(userId, roles);
        } else {
            roles.add(role);
        }
    }

    /**
     * 检查本地缓存中是否存在
     */
    private static boolean checkLocal(Long userId, String role) {
        Set<String> roleSet = userRoleMap.getIfPresent(userId);
        return roleSet != null && roleSet.contains(role);
    }

    /**
     * 清空指定用户的权限缓存
     */
    public static void clearAllUserROleLocalCache(Long userId) {
        userRoleMap.invalidate(userId);
    }

    /**
     * 清空本地所有用户权限缓存
     */
    public static void clearAllUserROleLocalCache() {
        userRoleMap.cleanUp();
    }

    /**
     * 获取当前用户默认语言
     */
    public static Integer getCurrentUserLanguage() {
        DecodedJWT decode = getDecodedToken();
        return decode.getClaim(AuthConstants.TOKEN_DETAIL_LANGUAGE).asInt();
    }


    /**
     * 多语言处理
     * <p/>
     * 这里使用了JDK8中的特性：行为参数化。
     *
     * @param functionForCN 如果是语言是中文，需要执行的方法
     * @param functionForFT 如果是语言是繁体，需要执行的方法
     * @param functionForEN 如果是语言是英文，需要执行的方法
     * @param <T>           泛型
     */
    public static <T> T invokeByLanguage(Callable<T> functionForCN,
                                         Callable<T> functionForFT,
                                         Callable<T> functionForEN) {
        return invokeByLanguage(getCurrentUserLanguage(), functionForCN, functionForFT, functionForEN);
    }


    /**
     * 多语言处理
     * <p/>
     * 这里使用了JDK8中的特性：行为参数化。
     *
     * @param language      语言
     * @param functionForCN 如果是语言是中文，需要执行的方法
     * @param functionForFT 如果是语言是繁体，需要执行的方法
     * @param functionForEN 如果是语言是英文，需要执行的方法
     * @param <T>           泛型
     */
    public static <T> T invokeByLanguage(int language,
                                         Callable<T> functionForCN,
                                         Callable<T> functionForFT,
                                         Callable<T> functionForEN) {
        try {
            switch (language) {
                case LAN_CN: {
                    return functionForCN.call();
                }
                case LAN_FT: {
                    return functionForFT.call();
                }
                case LAN_EN: {
                    return functionForEN.call();
                }
            }
        } catch (Exception e) {
            throw new RuntimeException("多语言转换失败！");
        }
        return null;
    }


    /**
     * 获取解密后的token信息
     */
    private static DecodedJWT getDecodedToken() {
        return JWT.decode(getCurrentToken());
    }

    public static String getCurrentToken() {
        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        String token = request.getHeader(AuthConstants.TOKEN_NAME);
        if (StringUtils.isEmpty(token)) {
            throw new UnauthorizedException();
        }
        return token;
    }


}
